﻿using System;
using System.Collections;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

namespace Natur.MapManager
{
    public enum UpdateType { Null = 0, Increase, Decrease, Clear }
    public enum LayerType { Bound = 0, Construction, Light, Location }
    public enum BoundType { Null = 0, Box, Plane, Sphere }
    public enum ObjectType { Empty = 0, Animation, Bound, Frame, Light, Location, Pinned, Relay, Spawn, Structure, Trigger }
    public enum PlayMode { Forward = 0, Backward }
    public enum ActionType { Activate = 0, Animation, Deactivate, Reset, Spawn, Trigger, Vertexed } 

    // ***** EVENTS & INTERFACES *****

    public delegate void ThrowFlag(Object sender, FlagArg eventArgs);
    public class FlagArg : EventArgs
    {
        public enum FlagType { Add = 0, Clear, FlagForProcessing, Remove }
        private FlagType flagType;

        public FlagArg(FlagType newType) { flagType = newType; }
        public FlagType Type { get { return flagType; } }

        public static FlagArg Add { get { return new FlagArg(FlagType.Add); } }
        public static FlagArg Clear { get { return new FlagArg(FlagType.Clear); } }
        public static FlagArg Remove { get { return new FlagArg(FlagType.Remove); } }
    }

    public interface IActivatable
    {
        bool IsActive { get; }
        void Activate();
        void Deactivate();
        void Reset();
    }

    public interface IAnimatable
    { bool IsAnimated { get; set; } }

    public interface ICollidable
    {
        BoundType BoundType { get; }
        Object CollisionBound { get; }
        float Elasticity { get; set; }
        float Friction { get; set; }
    }

    public interface IObject
    { Vector3 Position { get; set; } }

    public interface IOriented
    {
        Vector3 Forward { get; set; }
        Vector3 Backward { get; }
        Vector3 Up { get; set; }
        Vector3 Down { get; }
        Vector3 Left { get; }
        Vector3 Right { get; }
        Matrix World { get; }
    }

    public interface IDynamic : IUpdatable
    {
        Vector3 Velocity { get; set; }
        Matrix WorldVelocity { get; }
        
        void ApplyForce(Vector3 newForce);
        void ClearForces();
        void Shove(Vector3 shiftPosition);
    }

    public interface IDimensioned
    {
        float WRadius { get; set; }
        float HRadius { get; set; }
        float DRadius { get; set; }
        float Average { get; }
        Vector3 Max { get; }
    }

    public interface IRadial
    {
        float Radius { get; set; }
        Vector3 Max { get; }
    }

    public interface IVertexed
    {
        int StartVertex { get; set; }
        int VertexCount { get; set; }
        int StartIndex { get; set; }
        int IndexCount { get; set; }
    }

    public interface IPaletted
    {
        Vector2 TexStart { get; set; }
        Vector2 TexDimension { get; set; }
    }

    public interface IPin
    {
        MapEntity[] Pinned { get; }
        void AddPinned(MapEntity newPinnable);
        void RemovePinned(MapEntity removePinnable);
        void ClearPinned();
    }

    public interface IPinnable
    {
        MapEntity Pin { get; set; }
        bool IsPinned { get; set; }
    }

    public interface IUpdatable
    { Nullable<ActionType> Update(float tSeconds); }

    // Base Classes

    public class MapEntity
    {
        private ObjectType type;
        private static int countID;
        private int objectID;

        public MapEntity()
        { type = ObjectType.Empty; objectID = countID; countID++; }

        public MapEntity(ObjectType newType)
        { type = newType; objectID = countID; countID++; }

        public ObjectType Type { get { return type; } }
        public int ID { get { return objectID; } }

        public void OverrideID(int newID) { objectID = newID; }
    }

    public class SelectableObject : MapEntity, IObject, IPinnable
    {
        protected Vector3 position;
        private MapEntity pin = new MapEntity();
        private bool isPinned = false;

        public SelectableObject() : base()
        { position = Vector3.Zero; }

        public SelectableObject(ObjectType newType) : base(newType)
        { position = Vector3.Zero; }

        public SelectableObject(ObjectType newType, Vector3 newPos) : base(newType)
        { position = newPos; }

        public virtual Vector3 Position { get { return position; } set { position = value; } }
        public MapEntity Pin { get { return pin; } set { pin = value; } }
        public bool IsPinned { get { return isPinned; } set { isPinned = value; } }
    }

    public class SelectableStructure : SelectableObject, IOriented
    {
        protected Vector3 forward;
        protected Vector3 up;

        public SelectableStructure() : base()
        { forward = Vector3.Forward; up = Vector3.Up; }

        public SelectableStructure(ObjectType newType) : base(newType)
        { forward = Vector3.Forward; up = Vector3.Up; }

        public SelectableStructure(Vector3 newPos, Vector3 newForward, Vector3 newUp) : base(ObjectType.Empty, newPos)
        { forward = newForward; up = newUp; }

        public SelectableStructure(ObjectType newType, Vector3 newPos, Vector3 newForward, Vector3 newUp) : base(newType, newPos)
        { forward = newForward; up = newUp; }

        public Vector3 Forward { get { return forward; } set { forward = value; } }
        public Vector3 Backward { get { return -forward; } }
        public Vector3 Up { get { return up; } set { up = value; } }
        public Vector3 Down { get { return -up; } }
        public Vector3 Left { get { return Vector3.Normalize(-Vector3.Cross(forward, up)); } }
        public Vector3 Right { get { return Vector3.Normalize(Vector3.Cross(forward, up)); } }
        public Matrix World { get { return Matrix.CreateWorld(position, forward, up); } }
    }

    public class DimensionedStructure : SelectableStructure, IDimensioned
    {
        protected float wR;
        protected float hR;
        protected float dR;

        public DimensionedStructure() : base()
        { wR = 0; hR = 0; dR = 0; }

        public DimensionedStructure(Vector3 newPos, Vector3 newForward, Vector3 newUp) : base(newPos, newForward, newUp)
        { wR = 0; hR = 0; dR = 0; }

        public DimensionedStructure(float newW, float newH, float newD) : base()
        { wR = newW; hR = newH; dR = newD; }

        public DimensionedStructure(ObjectType newType, Vector3 newPos, Vector3 newForward, Vector3 newUp, float newW, float newH, float newD)
            : base(newType, newPos, newForward, newUp)
        { wR = newW; hR = newH; dR = newD; }

        public virtual float WRadius { get { return wR; } set { wR = value; } }
        public virtual float HRadius { get { return hR; } set { hR = value; } }
        public virtual float DRadius { get { return dR; } set { dR = value; } }
        public float Average { get { return (wR + hR + dR) / 3; } }
        public Vector3 Max { get { return new Vector3(wR, hR, dR); } }
    }

    public class VertexedStructure : DimensionedStructure, IVertexed
    {
        private int startVertex;
        private int vertexCount;
        private int startIndex;
        private int indexCount;

        public VertexedStructure() : base()
        { startVertex = 0; vertexCount = 0; startIndex = 0; indexCount = 0; }

        public VertexedStructure(Vector3 newPos, Vector3 newForward, Vector3 newUp) : base(newPos, newForward, newUp)
        { startVertex = 0; vertexCount = 0; startIndex = 0; indexCount = 0; }

        public VertexedStructure(int newStartVertex, int newVertexCount, int newStartIndex, int newIndexCount) : base()
        { startVertex = newStartVertex; vertexCount = newVertexCount; startIndex = newStartIndex; indexCount = newIndexCount; }

        public VertexedStructure(ObjectType newType, Vector3 newPos, Vector3 newForward, Vector3 newUp, float newW, float newH, float newD,
            int newStartVertex, int newVertexCount, int newStartIndex, int newIndexCount)
            : base(newType, newPos, newForward, newUp, newW, newH, newD)
        { startVertex = newStartVertex; vertexCount = newVertexCount; startIndex = newStartIndex; indexCount = newIndexCount; }

        public int StartVertex { get { return startVertex; } set { startVertex = value; } }
        public int VertexCount { get { return vertexCount; } set { vertexCount = value; } }
        public int StartIndex { get { return startIndex; } set { startIndex = value; } }
        public int IndexCount { get { return indexCount; } set { indexCount = value; } }
    }

    public class PalettedStructure : VertexedStructure, IPaletted
    {
        private Vector2 texStart;
        private Vector2 texDimension;

        public PalettedStructure() : base() { }

        public PalettedStructure(Vector3 newPos, Vector3 newForward, Vector3 newUp, float newWRadius, float newHRadius, float newDRadius,
            int newStartVertex, int newVertexCount, int newStartIndex, int newIndexCount, Vector2 newTexStart, Vector2 newTexDimension)
            : base(ObjectType.Structure, newPos, newForward, newUp, newWRadius, newHRadius, newDRadius, newStartVertex, newVertexCount, newStartIndex, newIndexCount)
        { texStart = newTexStart; texDimension = newTexDimension; }

        // ****** ACCESSORS ******
        public Vector2 TexStart { get { return texStart; } set { texStart = value; } }
        public Vector2 TexDimension { get { return texDimension; } set { texDimension = value; } }
    }

    // ***** SELECTABLES *****

    public class Light : SelectableStructure, IRadial
    {
        const float LIGHT_SCALE = 2f;

        public Color Color;
        public float Intensity;
        public float Range;

        public Light(Vector3 newPos, Vector3 newForward, Vector3 newUp, Color newColor, float newIntensity, float newRange)
            : base(ObjectType.Light, newPos, newForward, newUp)
        { Color = newColor; Intensity = newIntensity; Range = newRange; }

        public float Radius { get { return Intensity * LIGHT_SCALE; } set { Intensity = value / LIGHT_SCALE; } }

        public Vector3 Max { get { float newDim = Intensity * LIGHT_SCALE; return newDim * Vector3.Normalize(new Vector3(newDim, newDim, newDim)); } }
    }

    public class StaticStructure : PalettedStructure, IAnimatable
    {
        private bool isAnimated = false;

        public StaticStructure() : base() { }

        public StaticStructure(Vector3 newPos, Vector3 newForward, Vector3 newUp, float newWRadius, float newHRadius, float newDRadius,
            int newStartVertex, int newVertexCount, int newStartIndex, int newIndexCount, Vector2 newTexStart, Vector2 newTexDimension)
            : base(newPos, newForward, newUp, newWRadius, newHRadius, newDRadius, newStartVertex, newVertexCount, newStartIndex, newIndexCount, newTexStart, newTexDimension)
        { }

        public bool IsAnimated { get { return isAnimated; } set { isAnimated = value; } }
    }

    public class InteractiveStructure : PalettedStructure, IActivatable, ICollidable, IDynamic
    {
        private float elasticity = .1f;
        private float friction = .8f;
        public float Glide = .001f;

        private BoundType boundType;
        private Object bound;

        private Vector3 posState;
        private Vector3 forState;
        private Vector3 upState;
        private Vector3 externalForce = Vector3.Zero;
        private Vector3 velocity = Vector3.Zero;
        private Matrix worldVelocity = Matrix.Identity;
        private bool isActive = false;

        public InteractiveStructure() : base()
        { boundType = BoundType.Null; posState = Vector3.Zero; forState = Vector3.Zero; upState = Vector3.Zero; }

        public InteractiveStructure(Vector3 newPos, Vector3 newForward, Vector3 newUp, float newWRadius, float newHRadius, float newDRadius,
            int newStartVertex, int newVertexCount, int newStartIndex, int newIndexCount, Vector2 newTexStart, Vector2 newTexDimension,
            BoundType newBoundType)
            : base(newPos, newForward, newUp, newWRadius, newHRadius, newDRadius, newStartVertex, newVertexCount, newStartIndex,
            newIndexCount, newTexStart, newTexDimension)
        {
            boundType = newBoundType;
            posState = newPos;
            forState = newForward;
            upState = newUp;
            UpdateBound();
        }

        // ***** ACCESSORS *****

        public override float WRadius { get { return wR; } set { wR = value; UpdateBound(); } }
        public override float HRadius { get { return hR; } set { hR = value; UpdateBound(); } }
        public override float DRadius { get { return dR; } set { dR = value; UpdateBound(); } }

        public float Elasticity { get { return elasticity; } set { elasticity = value; } }
        public float Friction { get { return friction; } set { friction = value; } }

        public BoundType BoundType { get { return boundType; } }
        public Object CollisionBound
        {
            get
            {
                Object returnBound = bound;
                if (boundType == BoundType.Sphere)
                {
                    BoundingSphere returnSphere = (BoundingSphere)returnBound;
                    returnSphere.Center = position;
                    returnBound = returnSphere;
                }
                return returnBound; 
            }
        }
        public bool IsActive { get { return isActive; } }
        public Vector3 Velocity { get { return velocity; } set { velocity = value; } }
        public Matrix WorldVelocity { get { return worldVelocity; } }

        // ***** USER METHODS *****

        public void Activate()
        { isActive = true; }

        public void ApplyForce(Vector3 newForce)
        { externalForce += newForce; }

        public void BakeState()
        { posState = position; forState = forward; upState = up; }

        public void ClearForces()
        { externalForce = Vector3.Zero; }

        public void Deactivate()
        { isActive = false; }

        public void Reset()
        {
            externalForce = Vector3.Zero;
            velocity = Vector3.Zero;
            worldVelocity = Matrix.Identity;
            position = posState;
            forward = forState;
            up = upState;
        }

        public void Shove(Vector3 shiftPosition)
        {
            worldVelocity = Matrix.Invert(worldVelocity) * Matrix.CreateTranslation(shiftPosition) * worldVelocity;
            position += shiftPosition;
        }

        public Nullable<ActionType> Update(float tSeconds)
        {
            Nullable<ActionType> returnAction = ActionType.Vertexed;
            worldVelocity = Matrix.CreateWorld(position, forward, up);
            // Forces
            if (externalForce != Vector3.Zero)
            { velocity += externalForce; }
            // Glide
            if (velocity != Vector3.Zero)
            { velocity *= 1 - Glide; }
            // Update
            position += velocity * tSeconds;
            worldVelocity = Matrix.Invert(worldVelocity) * Matrix.CreateWorld(position, forward, up);
            return returnAction;
        }

        private void UpdateBound()
        {
            switch (boundType)
            {
                case BoundType.Box:
                    bound = new BoundingBox(-base.Max, base.Max);
                    break;
                case BoundType.Sphere:
                    bound = new BoundingSphere(Vector3.Zero, base.Max.Length());
                    break;
            }
        }
    }

    public class PinnedStructure : SelectableStructure, IPin
    {
        private List<MapEntity> pinned;

        public PinnedStructure() : base(ObjectType.Pinned) { pinned = new List<MapEntity>(); }

        public PinnedStructure(Vector3 newPos, Vector3 newForward, Vector3 newUp)
            : base(ObjectType.Pinned, newPos, newForward, newUp) { pinned = new List<MapEntity>(); }

        public PinnedStructure(Vector3 newPos, Vector3 newForward, Vector3 newUp, MapEntity[] pinObjects)
            : base(ObjectType.Pinned, newPos, newForward, newUp) { pinned = new List<MapEntity>(pinObjects); }

        // ****** PIN METHODS ******

        public void AddPinned(MapEntity newPinnable)
        { if (!pinned.Contains(newPinnable)) { pinned.Add(newPinnable); } }

        public void ClearPinned()
        { pinned.Clear(); }

        public MapEntity[] Pinned { get { return pinned.ToArray(); } }

        public void RemovePinned(MapEntity removePinnable)
        { if (pinned.Contains(removePinnable)) { pinned.Remove(removePinnable); } }
    }

    public abstract class Bound : SelectableObject, IAnimatable, ICollidable
    {
        private float friction = .5f;
        private float elasticity = .1f;
        private bool isAnimated = false;
        public bool IsLadder = false;

        public Bound() : base(ObjectType.Bound) { }

        public float Elasticity { get { return elasticity; } set { elasticity = value; } }
        public float Friction { get { return friction; } set { friction = value; } }

        public abstract BoundType BoundType { get; }
        public abstract Object CollisionBound { get; }
        public bool IsAnimated { get { return isAnimated; } set { isAnimated = value; } }
    }

    public class BoundBox : Bound, IOriented, IDimensioned
    {
        private BoundingBox bounds;
        new private Vector3 position;
        private Vector3 forward;
        private Vector3 up;

        public BoundBox(Vector3 newPosition, Vector3 newForward, Vector3 newUp, float newW, float newH, float newD)
            : base()
        {
            bounds = new BoundingBox(new Vector3(-newW, -newH, -newD), new Vector3(newW, newH, newD));
            position = newPosition; forward = newForward; up = newUp;
        }


        public override BoundType BoundType { get { return BoundType.Box; } }
        public override Object CollisionBound { get { return bounds; } }
        public override Vector3 Position { get { return position; } set { position = value; } }
        public Vector3 Forward { get { return forward; } set { forward = value; } }
        public Vector3 Backward { get { return -forward; } }
        public Vector3 Up { get { return up; } set { up = value; } }
        public Vector3 Down { get { return -up; } }
        public Vector3 Left { get { return Vector3.Normalize(-Vector3.Cross(forward, up)); } }
        public Vector3 Right { get { return Vector3.Normalize(Vector3.Cross(forward, up)); } }
        public Matrix World { get { return Matrix.CreateWorld(position, forward, up); } }

        public float WRadius
        {
            get { return bounds.Max.X; }
            set { bounds.Min = new Vector3(-value, bounds.Min.Y, bounds.Min.Z); bounds.Max = new Vector3(value, bounds.Max.Y, bounds.Max.Z); }
        }
        public float HRadius
        {
            get { return bounds.Max.Y; }
            set { bounds.Min = new Vector3(bounds.Min.X, -value, bounds.Min.Z); bounds.Max = new Vector3(bounds.Max.X, value, bounds.Max.Z); }
        }
        public float DRadius
        {
            get { return bounds.Max.Z; }
            set { bounds.Min = new Vector3(bounds.Min.X, bounds.Min.Y, -value); bounds.Max = new Vector3(bounds.Max.X, bounds.Max.Y, value); }
        }
        public float Average { get { return (bounds.Max.X + bounds.Max.Y + bounds.Max.Z) / 3; } }
        public Vector3 Max { get { return bounds.Max; } }
    }

    public class BoundSphere : Bound, IRadial
    {
        private BoundingSphere bounds;

        public BoundSphere(Vector3 newPos, float newR)
            : base()
        { bounds = new BoundingSphere(newPos, newR); }

        public override BoundType BoundType { get { return BoundType.Sphere; } }
        public override Object CollisionBound { get { return bounds; } }
        public override Vector3 Position { get { return bounds.Center; } set { bounds.Center = value; } }
        public float Radius { get { return bounds.Radius; } set { bounds.Radius = value; } }
        public Vector3 Max { get { float newDim = bounds.Radius; return newDim * Vector3.Normalize(new Vector3(newDim, newDim, newDim)); } }
    }

    public abstract class Location : SelectableObject, IObject, IAnimatable
    {
        private bool isInside;
        private bool lastStatus;
        private bool isAnimated = false;

        public Location() : base(ObjectType.Location) { }

        // ***** ACCESSORS *****

        public bool IsAnimated { get { return isAnimated; } set { isAnimated = value; } }

        public bool IsInside
        {
            get { return isInside; }
            set { lastStatus = isInside; isInside = value; }
        }

        public bool ToggleIn
        {
            get
            {
                if ((!lastStatus) && (isInside)) { return true; }
                else { return false; }
            }
        }

        public bool ToggleOut
        {
            get
            {
                if ((lastStatus) && (!isInside)) { return true; }
                else { return false; }
            }
        }

        // ***** USER METHODS *****

        public abstract Object Bounds { get; }
        public abstract Vector3 Position { get; set; }
    }

    public class LocationBox : Location, IOriented, IDimensioned
    {
        private BoundingBox bounds;
        private Vector3 position;
        private Vector3 forward;
        private Vector3 up;

        public LocationBox(Vector3 newPosition, Vector3 newForward, Vector3 newUp, float newW, float newH, float newD)
            : base()
        {
            bounds = new BoundingBox(new Vector3(-newW, -newH, -newD), new Vector3(newW, newH, newD));
            position = newPosition; forward = newForward; up = newUp;
        }

        public override Object Bounds { get { return bounds; } }
        public override Vector3 Position { get { return position; } set { position = value; } }
        public Vector3 Forward { get { return forward; } set { forward = value; } }
        public Vector3 Backward { get { return -forward; } }
        public Vector3 Up { get { return up; } set { up = value; } }
        public Vector3 Down { get { return -up; } }
        public Vector3 Left { get { return Vector3.Normalize(-Vector3.Cross(forward, up)); } }
        public Vector3 Right { get { return Vector3.Normalize(Vector3.Cross(forward, up)); } }
        public Matrix World { get { return Matrix.CreateWorld(position, forward, up); } }

        public float WRadius
        {
            get { return bounds.Max.X; }
            set { bounds.Min = new Vector3(-value, bounds.Min.Y, bounds.Min.Z); bounds.Max = new Vector3(value, bounds.Max.Y, bounds.Max.Z); }
        }
        public float HRadius
        {
            get { return bounds.Max.Y; }
            set { bounds.Min = new Vector3(bounds.Min.X, -value, bounds.Min.Z); bounds.Max = new Vector3(bounds.Max.X, value, bounds.Max.Z); }
        }
        public float DRadius
        {
            get { return bounds.Max.Z; }
            set { bounds.Min = new Vector3(bounds.Min.X, bounds.Min.Y, -value); bounds.Max = new Vector3(bounds.Max.X, bounds.Max.Y, value); }
        }
        public float Average { get { return (bounds.Max.X + bounds.Max.Y + bounds.Max.Z) / 3; } }
        public Vector3 Max { get { return bounds.Max; } }
    }

    public class LocationSphere : Location, IRadial
    {
        private BoundingSphere bounds;

        public LocationSphere(Vector3 newPos, float newR)
            : base()
        { bounds = new BoundingSphere(newPos, newR); }

        public override Object Bounds { get { return bounds; } }
        public override Vector3 Position { get { return bounds.Center; } set { bounds.Center = value; } }
        public float Radius { get { return bounds.Radius; } set { bounds.Radius = value; } }
        public Vector3 Max { get { float newDim = bounds.Radius; return newDim * Vector3.Normalize(new Vector3(newDim, newDim, newDim)); } }
    }

    public class Spawn : SelectableObject, IRadial, IActivatable, IUpdatable
    {
        public int SpawnID;
        private int count;
        private float delay;
        private float radius = 2;
        private bool isActive = false;
        private int remaining;
        private float currentDelay;

        public Spawn() : base(ObjectType.Spawn)
        { SpawnID = 0; count = 0; remaining = 0; delay = 0; currentDelay = 0; }

        public Spawn(Vector3 newPos) : base(ObjectType.Spawn, newPos)
        { SpawnID = 0; count = 0; remaining = 0; delay = 0; currentDelay = 0; }

        public Spawn(Vector3 newPos, int newID) : base(ObjectType.Spawn, newPos)
        { SpawnID = newID; count = 0; remaining = 0; delay = 0; currentDelay = 0; }

        public Spawn(Vector3 newPos, int newID, int newCount) : base(ObjectType.Spawn, newPos)
        { SpawnID = newID; count = newCount; remaining = newCount; delay = 0; currentDelay = 0; }

        public Spawn(Vector3 newPos, int newID, int newCount, float newDelay) : base(ObjectType.Spawn, newPos)
        { SpawnID = newID; count = newCount; remaining = newCount; delay = newDelay; currentDelay = newDelay; }

        // ***** ACCESSORS *****

        public int Count { get { return count; } set { count = value; remaining = value; } }

        public float Delay { get { return delay; } set { delay = value; currentDelay = value; } }

        public bool IsActive { get { return isActive; } }

        public float Radius { get { return radius; } set { } }

        public Vector3 Max { get { float newDim = radius; return newDim * Vector3.Normalize(new Vector3(newDim, newDim, newDim)); } }

        // ***** USER METHODS *****

        public void Activate() { isActive = true; }

        public void Deactivate() { isActive = false; }

        public void Reset()
        { remaining = count; currentDelay = delay; }

        public Nullable<ActionType> Update(float tSeconds)
        {
            Nullable<ActionType> returnAction = null;
            if (isActive)
            {
                currentDelay -= tSeconds;
                if (currentDelay <= 0)
                {
                    remaining--;
                    if ((remaining > 0) || (count == 0))
                    {
                        currentDelay = delay;
                        returnAction = ActionType.Spawn;
                    }
                    else { Reset(); isActive = false; }
                }
            }
            return returnAction;
        }
    }

    public class Relay : SelectableObject, IRadial
    {
        private float radius = 1;
        private List<Relay> lstRelays = new List<Relay>();

        public Relay() : base(ObjectType.Relay) { }

        public Relay(Vector3 newPos) : base(ObjectType.Relay, newPos) { }

        // ***** ACCESSORS *****

        public float Radius { get { return radius; } set { } }

        public Vector3 Max { get { float newDim = radius; return newDim * Vector3.Normalize(new Vector3(newDim, newDim, newDim)); } }

        public Relay[] Relays { get { return lstRelays.ToArray(); } }

        // ***** USER METHODS *****

        public void LinkRelay(Relay newRelay)
        {
            if (!lstRelays.Contains(newRelay))
            { lstRelays.Add(newRelay); }
        }

    }

    // ***** ANIMATIONS *****

    public class Animation : MapEntity, IActivatable, IUpdatable
    {
        const float THRESH_DURATION = 1;

        private FlagList<MapEntity> animatedObjects = new FlagList<MapEntity>(1);
        private List<AnimationFrame> frames;
        private float duration;
        public float PlayPosition;

        public PlayMode PlayMode;
        public float Speed;
        public bool Repeat = false;
        private bool isActive = false;

        public Animation(float newDuration, ThrowFlag newFlagCallback)
            : base(ObjectType.Animation)
        {
            duration = newDuration;
            Speed = 1;
            PlayMode = PlayMode.Forward;
            PlayPosition = 0;

            frames = new List<AnimationFrame>(2);
            frames.Add(new AnimationFrame(0));
            frames.Add(new AnimationFrame(newDuration));

            // Link event
            if (newFlagCallback != null) { animatedObjects.FlagEvent += newFlagCallback; }
        }

        public Animation(float newDuration, float newSpeed, ThrowFlag newFlagCallback)
            : base(ObjectType.Animation)
        {
            duration = newDuration;
            Speed = newSpeed;
            PlayMode = PlayMode.Forward;
            PlayPosition = 0;

            frames = new List<AnimationFrame>(2);
            frames.Add(new AnimationFrame(0));
            frames.Add(new AnimationFrame(newDuration));

            // Link event
            if (newFlagCallback != null) { animatedObjects.FlagEvent += newFlagCallback; }
        }

        public Animation(float newDuration, float newSpeed, PlayMode newPlayMode, ThrowFlag newFlagCallback)
            : base(ObjectType.Animation)
        {
            duration = newDuration;
            Speed = newSpeed;
            PlayMode = newPlayMode;
            switch (newPlayMode)
            {
                case PlayMode.Forward:
                    PlayPosition = 0;
                    break;
                case PlayMode.Backward:
                    PlayPosition = newDuration;
                    break;
            }

            frames = new List<AnimationFrame>(2);
            frames.Add(new AnimationFrame(0));
            frames.Add(new AnimationFrame(newDuration));

            // Link event
            if (newFlagCallback != null) { animatedObjects.FlagEvent += newFlagCallback; }
        }

        // ***** ACCESSORS ***** 

        public MapEntity[] AnimatedObjects { get { return animatedObjects.ToArray(); } }

        public float Duration
        {
            get { return duration; }
            set
            {
                if (value >= THRESH_DURATION)
                {
                    float changePercent = value / duration;
                    for (int i = 0; i < frames.Count - 1; i++)
                    { frames[i].TimePosition *= changePercent; }
                    frames[frames.Count - 1].TimePosition = value; // ensure float precision
                    duration = value;
                }
            }
        }

        public AnimationFrame[] Frames { get { return frames.ToArray(); } set { frames = new List<AnimationFrame>(value); } }

        public bool IsActive { get { return isActive; } }

        // ***** USER METHODS ***** 

        public void Activate() { isActive = true; }

        public void AddObject(params MapEntity[] fromSelected)
        {
            for (int pS = 0; pS < fromSelected.Length; pS++)
            {
                // Add individual objects if pinned
                PinnedStructure pinHold = fromSelected[pS] as PinnedStructure;
                if (pinHold != null)
                {
                    MapEntity[] pinnedObjects = pinHold.Pinned;
                    for (int i = 0; i < pinnedObjects.Length; i++)
                    { AddObject(pinnedObjects[i]); }
                }
                // Add objects and states for each frame 
                else if (!animatedObjects.Contains(fromSelected[pS]) && (fromSelected[pS] is IAnimatable))
                {
                    ((IAnimatable)fromSelected[pS]).IsAnimated = true;
                    animatedObjects.Add(fromSelected[pS]);
                    for (int i = 0; i < frames.Count; i++)
                    {
                        if (fromSelected[pS] is IObject)
                        {
                            if (fromSelected[pS] is IOriented)
                            {
                                frames[i].States.Add(new ObjectState(((IObject)fromSelected[pS]).Position,
                                        ((IOriented)fromSelected[pS]).Forward, ((IOriented)fromSelected[pS]).Up));
                            }
                            else
                            { frames[i].States.Add(new ObjectState(((IObject)fromSelected[pS]).Position, Vector3.Forward, Vector3.Up)); }
                        }
                    }
                } // if animatable
            }
        }

        public void AddFrame(float newTimePosition)
        {
            // Maintain frame order by time position 
            bool frameFound = false;
            int frameIndex = 1;
            while (frameIndex < frames.Count)
            {
                if (newTimePosition < frames[frameIndex].TimePosition)
                { frameFound = true; break; }
                else if (newTimePosition == frames[frameIndex].TimePosition)
                {
                    if (frameIndex == frames.Count - 1)
                    {
                        newTimePosition -= (newTimePosition - frames[frameIndex - 1].TimePosition) / 2;
                        frameFound = true; break;
                    }
                    else
                    {
                        newTimePosition += (frames[frameIndex + 1].TimePosition - newTimePosition) / 2;
                        frameIndex++; frameFound = true; break;
                    }
                }
                frameIndex++;
            }
            // Create frame 
            if (frameFound)
            { frames.Insert(frameIndex, frames[frameIndex - 1].Clone(newTimePosition)); }
        }

        public void AddFrame(AnimationFrame newFrame)
        {
            // Maintain frame order by time position 
            bool frameFound = false;
            int frameIndex = 1;
            while (frameIndex < frames.Count)
            {
                if (newFrame.TimePosition < frames[frameIndex].TimePosition)
                { frameFound = true; break; }
                else if (newFrame.TimePosition == frames[frameIndex].TimePosition)
                {
                    if (frameIndex == frames.Count - 1)
                    {
                        newFrame.TimePosition -= (newFrame.TimePosition - frames[frameIndex - 1].TimePosition) / 2;
                        frameFound = true; break;
                    }
                    else
                    {
                        newFrame.TimePosition += (frames[frameIndex + 1].TimePosition - newFrame.TimePosition) / 2;
                        frameIndex++; frameFound = true; break;
                    }
                }
                frameIndex++;
            }
            // Create frame 
            if (frameFound) { frames.Insert(frameIndex, newFrame); }
        }

        public void BakeState(bool autoState, params MapEntity[] fromSelected)
        {
            // Locate frame
            AnimationFrame selectedFrame = null;
            for (int i = 0; i < fromSelected.Length; i++)
            {
                if (fromSelected[i].Type == ObjectType.Frame)
                { selectedFrame = fromSelected[i] as AnimationFrame; }
            }
            // Update frame
            if (selectedFrame != null)
            {
                for (int i = 0; i < fromSelected.Length; i++)
                {
                    // if Pin, bake individual objects
                    if (fromSelected[i] is IPin)
                    {
                        MapEntity[] pinned = ((IPin)fromSelected[i]).Pinned;
                        for (int a = 0; a < pinned.Length; a++)
                        { BakeState(autoState, selectedFrame, pinned[a]); }
                    }
                    else
                    {
                        int stateIndex = GetStateIndex(fromSelected[i]);
                        if (stateIndex != -1)
                        {
                            Vector3 newPos = Vector3.Zero;
                            Vector3 newForward = Vector3.Forward;
                            Vector3 newUp = Vector3.Up;
                            if (fromSelected[i] is IObject)
                            { newPos = ((IObject)fromSelected[i]).Position; }
                            if (fromSelected[i] is IOriented)
                            {
                                newForward = ((IOriented)fromSelected[i]).Forward;
                                newUp = ((IOriented)fromSelected[i]).Up;
                            }
                            // Update
                            selectedFrame.States[stateIndex] = new ObjectState(newPos, newForward, newUp);
                            if (autoState)
                            {
                                AnimationFrame[] aryFrames = GetFrames(selectedFrame.TimePosition + .05f, duration);
                                for (int a = 0; a < aryFrames.Length; a++)
                                { aryFrames[a].States[stateIndex] = new ObjectState(newPos, newForward, newUp); }
                            }
                        }
                    }
                }
            }
        }

        public void ClearObjects()
        {
            for (int i = 0; i < animatedObjects.Count; i++)
            { ((IAnimatable)animatedObjects[i]).IsAnimated = false; }
            animatedObjects.Clear();
        }

        public void Deactivate() { isActive = false; }

        public void DeleteRange(float minTime, float maxTime)
        {
            for (int i = 1; i < frames.Count - 1; i++)
            {
                if ((frames[i].TimePosition >= minTime) && (frames[i].TimePosition <= maxTime))
                { frames.RemoveAt(i); i--; }
            }
        }

        public AnimationFrame GetFrame(float minTime, float maxTime)
        {
            AnimationFrame returnFrame = null;
            for (int i = 0; i < frames.Count; i++)
            {
                if ((frames[i].TimePosition >= minTime) && (frames[i].TimePosition <= maxTime))
                { returnFrame = frames[i]; break; }
            }
            return returnFrame;
        }

        public AnimationFrame[] GetFrames(float minTime, float maxTime)
        {
            List<AnimationFrame> returnFrames = new List<AnimationFrame>();
            for (int i = 0; i < frames.Count; i++)
            {
                if ((frames[i].TimePosition >= minTime) && (frames[i].TimePosition <= maxTime))
                { returnFrames.Add(frames[i]); }
            }
            return returnFrames.ToArray();
        }

        public ObjectState GetState(int stateIndex, float timePosition)
        {
            ObjectState returnState = new ObjectState(Vector3.Zero, Vector3.Forward, Vector3.Up);
            if (timePosition <= frames[0].TimePosition)
            { returnState = frames[0].States[stateIndex]; }
            else if (timePosition >= frames[frames.Count - 1].TimePosition)
            { returnState = frames[frames.Count - 1].States[stateIndex]; }
            else
            {
                for (int i = 0; i < frames.Count - 1; i++)
                {
                    if ((timePosition > frames[i].TimePosition) && (timePosition <= frames[i + 1].TimePosition))
                    {
                        float lerpAmount = (timePosition - frames[i].TimePosition) / (frames[i + 1].TimePosition - frames[i].TimePosition);
                        returnState.Position = Vector3.Lerp(frames[i].States[stateIndex].Position, frames[i + 1].States[stateIndex].Position, lerpAmount);
                        returnState.Forward = Vector3.Lerp(frames[i].States[stateIndex].Forward, frames[i + 1].States[stateIndex].Forward, lerpAmount);
                        returnState.Up = Vector3.Lerp(frames[i].States[stateIndex].Up, frames[i + 1].States[stateIndex].Up, lerpAmount);
                        break;
                    }
                }
            }
            return returnState;
        }

        public int GetStateIndex(MapEntity fromSelected)
        {
            int foundState = -1;
            for (int i = 0; i < animatedObjects.Count; i++)
            {
                if (animatedObjects[i] == fromSelected)
                { foundState = i; break; }
            }
            return foundState;
        }

        public void RemoveObject(params MapEntity[] fromSelected)
        {
            for (int pS = 0; pS < fromSelected.Length; pS++)
            {
                int stateIndex = GetStateIndex(fromSelected[pS]);
                if (stateIndex != -1)
                {
                    // Remove from frames
                    for (int i = 0; i < frames.Count; i++)
                    { frames[i].States.RemoveAt(stateIndex); }
                    // Remove from animation
                    ((IAnimatable)fromSelected[pS]).IsAnimated = false;
                    animatedObjects.RemoveAt(stateIndex);
                }
            }
        }

        public void Reset()
        {
            switch (PlayMode)
            {
                case PlayMode.Backward:
                    PlayPosition = duration;
                    break;
                case PlayMode.Forward:
                    PlayPosition = 0;
                    break;
            }
        }

        public Nullable<ActionType> Update(float tSeconds)
        {
            Nullable<ActionType> returnAction = null;
            if (isActive)
            {
                returnAction = ActionType.Animation;
                switch (PlayMode)
                {
                    case PlayMode.Backward:
                        PlayPosition -= tSeconds;
                        if (PlayPosition <= 0)
                        {
                            if (Repeat)
                            { PlayPosition = duration; }
                            else { PlayPosition = duration; isActive = false; returnAction = ActionType.Deactivate; }
                        }
                        break;
                    case PlayMode.Forward:
                        PlayPosition += tSeconds;
                        if (PlayPosition >= duration)
                        {
                            if (Repeat) { PlayPosition = 0; }
                            else { PlayPosition = 0; isActive = false; returnAction = ActionType.Deactivate; }
                        }
                        break;
                }
            }
            return returnAction;
        }
    }

    public class AnimationFrame : MapEntity
    {
        public float TimePosition;
        public List<ObjectState> States;

        public AnimationFrame(float newTimePosition)
            : base(ObjectType.Frame)
        { TimePosition = newTimePosition; States = new List<ObjectState>(1); }

        public AnimationFrame(float newTimePosition, ObjectState[] newStates)
            : base(ObjectType.Frame)
        { TimePosition = newTimePosition; States = new List<ObjectState>(newStates); }

        public AnimationFrame Clone()
        {
            ObjectState[] newStates = new ObjectState[States.Count];
            for (int i = 0; i < newStates.Length; i++)
            { newStates[i] = new ObjectState(States[i].Position, States[i].Forward, States[i].Up); }
            AnimationFrame returnFrame = new AnimationFrame(TimePosition, newStates);
            return returnFrame;
        }

        public AnimationFrame Clone(float newTimePosition)
        {
            ObjectState[] newStates = new ObjectState[States.Count];
            for (int i = 0; i < newStates.Length; i++)
            { newStates[i] = new ObjectState(States[i].Position, States[i].Forward, States[i].Up); }
            AnimationFrame returnFrame = new AnimationFrame(newTimePosition, newStates);
            return returnFrame;
        }
    }

    // ***** TRIGGERS *****

    public abstract class Trigger : MapEntity, IActivatable
    {
        protected MapEntity targetObject;
        public int Occurences;
        protected int remaining;
        protected bool isActive;

        public Trigger()
            : base(ObjectType.Trigger)
        { targetObject = null; Occurences = 0; remaining = 0; }

        public Trigger(MapEntity newTarget)
            : base(ObjectType.Trigger)
        { targetObject = newTarget; Occurences = 0; remaining = 0; }

        public Trigger(MapEntity newTarget, int newOccurences)
            : base(ObjectType.Trigger)
        { targetObject = newTarget; Occurences = newOccurences; remaining = 0; }

        public bool IsActive { get { return isActive; } }
        public MapEntity TargetObject { get { return targetObject; } }

        public void AddTarget(MapEntity newTarget)
        {
            if (newTarget is IActivatable)
            { targetObject = newTarget; }
        }

        public void Activate() { isActive = true; }
        public void Deactivate() { isActive = false; }
        public abstract void Reset();
    }

    public class CommandTrigger : Trigger
    {
        public int CommandIndex;

        public CommandTrigger()
            : base()
        { CommandIndex = 0; }

        public CommandTrigger(MapEntity newTarget)
            : base(newTarget)
        { CommandIndex = 0; }

        public CommandTrigger(MapEntity newTarget, int newCommand)
            : base(newTarget)
        { CommandIndex = newCommand; }

        public CommandTrigger(MapEntity newTarget, int newCommand, int newOccurrences)
            : base(newTarget, newOccurrences)
        { CommandIndex = newCommand; }

        // ****** USER METHODS ******

        public void ActivateCommand()
        {
            if (IsActive)
            {
                remaining--;
                if ((remaining == 0) && (Occurences != 0))
                { Reset(); isActive = false; }
            }
        }

        public override void Reset() { remaining = Occurences; }
    }

    public class ProximityTrigger : Trigger, IUpdatable
    {
        public Location Location;

        public ProximityTrigger() : base() { Location = null; }

        public ProximityTrigger(MapEntity newTarget) : base(newTarget) { Location = null; }

        public ProximityTrigger(MapEntity newTarget, Location newLocation) : base(newTarget) { Location = newLocation; }

        public ProximityTrigger(MapEntity newTarget, Location newLocation, int newOccurrences)
            : base(newTarget, newOccurrences)
        { Location = newLocation; }

        // ***** USER METHODS ******

        public override void Reset()
        { remaining = Occurences; }

        public Nullable<ActionType> Update(float tSeconds)
        {
            Nullable<ActionType> returnAction = null;
            if (isActive && (Location != null) && (targetObject != null))
            {
                if (Location.ToggleIn)
                {
                    remaining--;
                    if ((remaining == 0) && (Occurences != 0))
                    { Reset(); isActive = false; }
                    returnAction = ActionType.Trigger;
                }
            }
            return returnAction;
        }
    }

    public class TimedTrigger : Trigger, IUpdatable
    {
        public float Delay;
        private float currentDelay = 0;

        public TimedTrigger()
            : base()
        { Delay = 0; }

        public TimedTrigger(MapEntity newTarget)
            : base(newTarget)
        { Delay = 0; }

        public TimedTrigger(MapEntity newTarget, float newDelay)
            : base(newTarget)
        { Delay = newDelay; }

        public TimedTrigger(MapEntity newTarget, float newDelay, int newOccurrences)
            : base(newTarget, newOccurrences)
        { Delay = newDelay; }

        // ***** USER METHODS *****

        public override void Reset()
        { remaining = Occurences; currentDelay = Delay; }

        public Nullable<ActionType> Update(float tSeconds)
        {
            Nullable<ActionType> returnAction = null;
            if (isActive && (targetObject != null))
            {
                currentDelay -= tSeconds;
                if (currentDelay <= 0)
                {
                    remaining--;
                    if ((remaining > 0) || (Occurences == 0))
                    { currentDelay = Delay; }
                    else { Reset(); isActive = false; }
                    returnAction = ActionType.Trigger;
                }
            }
            return returnAction;
        }
    }

    // ***** General *****

    public struct ObjectState
    {
        public Vector3 Position;
        public Vector3 Forward;
        public Vector3 Up;

        public ObjectState(Vector3 newPos, Vector3 newForward, Vector3 newUp)
        { Position = newPos; Forward = newForward; Up = newUp; }
    }

    public struct CollisionData
    {
        public bool Collision;
        public Vector3[] WorldSurfaces;
        public Nullable<float>[] HitDistances;
        public float[] Elasticities;
        public float[] Frictions;

        public CollisionData(bool isCollision, Vector3[] newWorldSurfaces, Nullable<float>[] newHitDistances, float[] newElasticities, float[] newFrictions)
        {
            Collision = isCollision;
            WorldSurfaces = newWorldSurfaces;
            HitDistances = newHitDistances;
            Elasticities = newElasticities;
            Frictions = newFrictions;
        }
    }

    public struct CustomVD : IVertexType
    {
        Vector3 vPosition;
        Vector3 vNormal;
        Vector2 texCoord;
        Color lightColor;

        public readonly static VertexDeclaration VertexDeclaration = new VertexDeclaration
        (
            new VertexElement(0, VertexElementFormat.Vector3, VertexElementUsage.Position, 0),
            new VertexElement(12, VertexElementFormat.Vector3, VertexElementUsage.Normal, 0),
            new VertexElement(24, VertexElementFormat.Vector2, VertexElementUsage.TextureCoordinate, 0),
            new VertexElement(32, VertexElementFormat.Color, VertexElementUsage.Color, 0)
        );

        public CustomVD(Vector3 newPosition, Vector3 newNormal, Vector2 newTexCoord, Color newLightColor)
        {
            vPosition = newPosition; vNormal = newNormal;
            texCoord = newTexCoord; lightColor = newLightColor;
        }

        // ****** ACCESSORS ******
        public Vector3 Position
        {
            get { return vPosition; }
            set { vPosition = value; }
        }

        public Vector3 Normal
        {
            get { return vNormal; }
            set { vNormal = value; }
        }

        public Vector2 TexCoord
        {
            get { return texCoord; }
            set { texCoord = value; }
        }

        public Color LightColor
        {
            get { return lightColor; }
            set { lightColor = value; }
        }

        VertexDeclaration IVertexType.VertexDeclaration
        {
            get { return VertexDeclaration; }
        }
    }

    public class FlagList<T> : List<T>
    {
        public FlagList() : base() { }
        public FlagList(int capacity) : base(capacity) { }
        public FlagList(FlagList<T> newList) : base(newList) { }
        public FlagList(T[] newArray) : base(newArray) { }

        // Flag event
        public event ThrowFlag FlagEvent;
        protected virtual void OnFlag(FlagArg eventArgs)
        { if (FlagEvent != null) { FlagEvent(this, eventArgs); } }

        // Main overrides
        public new void Add(T entity)
        {
            base.Add(entity);
            OnFlag(FlagArg.Add);
        }

        public new void Clear()
        {
            base.Clear();
            OnFlag(FlagArg.Clear);
        }

        public new void Remove(T entity)
        {
            base.Remove(entity);
            OnFlag(FlagArg.Remove);
        }

        public new void RemoveAt(int index)
        {
            base.RemoveAt(index);
            OnFlag(FlagArg.Remove);
        }
    }
}
